import React, { useRef, useEffect, useCallback, forwardRef, useState } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import toast from 'react-hot-toast';
import path from 'utils/common/path';
import { uuid } from 'utils/common';
import Modal from 'components/Modal';
import { useDispatch, useSelector } from 'react-redux';
import { newEphemeralHttpRequest } from 'providers/ReduxStore/slices/collections';
import { newHttpRequest, newGrpcRequest, newWsRequest } from 'providers/ReduxStore/slices/collections/actions';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector';
import { getDefaultRequestPaneTab } from 'utils/collections';
import { getRequestFromCurlCommand } from 'utils/curl';
import { IconArrowBackUp, IconCaretDown, IconEdit } from '@tabler/icons';
import { sanitizeName, validateName, validateNameError } from 'utils/common/regex';
import Dropdown from 'components/Dropdown';
import PathDisplay from 'components/PathDisplay';
import Portal from 'components/Portal';
import Help from 'components/Help';
import StyledWrapper from './StyledWrapper';
import SingleLineEditor from 'components/SingleLineEditor/index';
import { useTheme } from 'styled-components';

const NewRequest = ({ collectionUid, item, isEphemeral, onClose }) => {
  const dispatch = useDispatch();
  const inputRef = useRef();

  const storedTheme = useTheme();

  const collection = useSelector((state) => state.collections.collections?.find((c) => c.uid === collectionUid));
  const [curlRequestTypeDetected, setCurlRequestTypeDetected] = useState(null);
  const [showFilesystemName, toggleShowFilesystemName] = useState(false);

  const dropdownTippyRef = useRef();
  const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);

  const advancedDropdownTippyRef = useRef();
  const onAdvancedDropdownCreate = (ref) => (advancedDropdownTippyRef.current = ref);

  const Icon = forwardRef((props, ref) => {
    return (
      <div ref={ref} className="flex items-center justify-end auth-type-label select-none">
        {curlRequestTypeDetected === 'http-request' ? 'HTTP' : 'GraphQL'}
        <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
      </div>
    );
  });

  // This function analyzes a given cURL command string and determines whether the request is a GraphQL or HTTP request.
  const identifyCurlRequestType = (url, headers, body) => {
    if (url.endsWith('/graphql')) {
      setCurlRequestTypeDetected('graphql-request');
      return;
    }

    const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type')?.value;
    if (contentType && contentType.includes('application/graphql')) {
      setCurlRequestTypeDetected('graphql-request');
      return;
    }

    setCurlRequestTypeDetected('http-request');
  };

  const curlRequestTypeChange = (type) => {
    setCurlRequestTypeDetected(type);
  };

  const [isEditing, toggleEditing] = useState(false);

  const formik = useFormik({
    enableReinitialize: true,
    initialValues: {
      requestName: '',
      filename: '',
      requestType: 'http-request',
      requestUrl: '',
      requestMethod: 'GET',
      curlCommand: ''
    },
    validationSchema: Yup.object({
      requestName: Yup.string()
        .trim()
        .min(1, 'must be at least 1 character')
        .max(255, 'must be 255 characters or less')
        .required('name is required'),
      filename: Yup.string()
        .trim()
        .min(1, 'must be at least 1 character')
        .max(255, 'must be 255 characters or less')
        .required('filename is required')
        .test('is-valid-filename', function (value) {
          const isValid = validateName(value);
          return isValid ? true : this.createError({ message: validateNameError(value) });
        })
        .test(
          'not-reserved',
          `The file names "collection" and "folder" are reserved in bruno`,
          (value) => !['collection', 'folder'].includes(value)
        ),
      curlCommand: Yup.string().when('requestType', {
        is: (requestType) => requestType === 'from-curl',
        then: Yup.string()
          .min(1, 'must be at least 1 character')
          .required('curlCommand is required')
          .test({
            name: 'curlCommand',
            message: `Invalid cURL Command`,
            test: (value) => getRequestFromCurlCommand(value) !== null
          })
      })
    }),
    onSubmit: (values) => {
      const isGrpcRequest = values.requestType === 'grpc-request';
      const isWsRequest = values.requestType === 'ws-request';
      const filename = values.filename;

      if (isGrpcRequest) {
        dispatch(
          newGrpcRequest({
            requestName: values.requestName,
            filename: filename,
            requestType: values.requestType,
            requestUrl: values.requestUrl,
            collectionUid: collection.uid,
            itemUid: item ? item.uid : null
          })
        )
          .then(() => {
            toast.success('New request created!');
            onClose();
          })
          .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));

        // will need to handle import from grpcurl command when we support it, now it is just for creating new requests
      } else if (isWsRequest) {
        dispatch(newWsRequest({
          requestName: values.requestName,
          requestMethod: values.requestMethod,
          filename: filename,
          requestType: values.requestType,
          requestUrl: values.requestUrl,
          collectionUid: collection.uid,
          itemUid: item ? item.uid : null
        }))
          .then(() => {
            toast.success('New request created!');
            onClose();
          })
          .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
      } else if (isEphemeral) {
        const uid = uuid();
        dispatch(
          newEphemeralHttpRequest({
            uid: uid,
            requestName: values.requestName,
            filename: filename,
            requestType: values.requestType,
            requestUrl: values.requestUrl,
            requestMethod: values.requestMethod,
            collectionUid: collectionUid
          })
        )
          .then(() => {
            dispatch(
              addTab({
                uid: uid,
                collectionUid: collectionUid,
                requestPaneTab: getDefaultRequestPaneTab({ type: values.requestType })
              })
            );
            onClose();
          })
          .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
      } else if (values.requestType === 'from-curl') {
        const request = getRequestFromCurlCommand(values.curlCommand, curlRequestTypeDetected);
        const settings = { encodeUrl: false };

        dispatch(
          newHttpRequest({
            requestName: values.requestName,
            filename: filename,
            requestType: curlRequestTypeDetected,
            requestUrl: request.url,
            requestMethod: request.method,
            collectionUid: collectionUid,
            itemUid: item ? item.uid : null,
            headers: request.headers,
            body: request.body,
            auth: request.auth,
            settings: settings
          })
        )
          .then(() => {
            toast.success('New request created!');
            onClose();
          })
          .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
      } else {
        dispatch(
          newHttpRequest({
            requestName: values.requestName,
            filename: filename,
            requestType: values.requestType,
            requestUrl: values.requestUrl,
            requestMethod: values.requestMethod,
            collectionUid: collectionUid,
            itemUid: item ? item.uid : null
          })
        )
          .then(() => {
            toast.success('New request created!');
            onClose();
          })
          .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
      }
    }
  });

  useEffect(() => {
    if (inputRef && inputRef.current) {
      inputRef.current.focus();
    }
  }, [inputRef]);

  const onSubmit = () => formik.handleSubmit();

  const handlePaste = useCallback(
    (event) => {
      const clipboardData = event.clipboardData || window.clipboardData;
      const pastedData = clipboardData.getData('Text');

      // Check if pasted data looks like a cURL command
      const curlCommandRegex = /^\s*curl\s/i;
      if (curlCommandRegex.test(pastedData)) {
        // Switch to the 'from-curl' request type
        formik.setFieldValue('requestType', 'from-curl');
        formik.setFieldValue('curlCommand', pastedData);

        // Identify the request type
        const request = getRequestFromCurlCommand(pastedData);
        if (request) {
          identifyCurlRequestType(request.url, request.headers, request.body);
        }

        // Prevent the default paste behavior to avoid pasting into the textarea
        event.preventDefault();
      }
    },
    [formik]
  );

  const handleCurlCommandChange = (event) => {
    formik.handleChange(event);

    if (event.target.name === 'curlCommand') {
      const curlCommand = event.target.value;
      const request = getRequestFromCurlCommand(curlCommand);
      if (request) {
        identifyCurlRequestType(request.url, request.headers, request.body);
      }
    }
  };

  const AdvancedOptions = forwardRef((props, ref) => {
    return (
      <div ref={ref} className="flex mr-2 text-link cursor-pointer items-center">
        <button className="btn-advanced" type="button">
          Options
        </button>
        <IconCaretDown className="caret ml-1" size={14} strokeWidth={2} />
      </div>
    );
  });

  return (
    <Portal>
      <StyledWrapper>
        <Modal size="md" title="New Request" hideFooter handleCancel={onClose}>
          <form
            className="bruno-form"
            onSubmit={formik.handleSubmit}
            onKeyDown={(e) => {
              if (e.key === 'Enter') {
                e.preventDefault();
                formik.handleSubmit();
              }
            }}
          >
            <div>
              <label htmlFor="requestName" className="block font-medium">
                Type
              </label>

              <div className="mt-2 grid grid-cols-3 gap-2">
                <div className="flex flex-col gap-2">
                  <div className="flex items-center gap-2">
                    <input
                      type="radio"
                      id="http-request"
                      name="requestType"
                      value="http-request"
                      checked={formik.values.requestType === 'http-request'}
                      onChange={formik.handleChange}
                      data-testid="http-request"
                    />
                    <label htmlFor="http-request" className="ml-1 cursor-pointer select-none">
                      HTTP
                    </label>
                  </div>
                  <div className="flex items-center gap-2">
                    <input
                      type="radio"
                      id="graphql-request"
                      name="requestType"
                      value="graphql-request"
                      checked={formik.values.requestType === 'graphql-request'}
                      onChange={formik.handleChange}
                      data-testid="graphql-request"
                    />
                    <label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
                      GraphQL
                    </label>
                  </div>
                </div>

                <div className="flex flex-col gap-2">
                  <div className="flex items-center gap-2">
                    <input
                      type="radio"
                      id="grpc-request"
                      name="requestType"
                      value="grpc-request"
                      checked={formik.values.requestType === 'grpc-request'}
                      onChange={formik.handleChange}
                      data-testid="grpc-request"
                    />
                    <label htmlFor="grpc-request" className="ml-1 cursor-pointer select-none">
                      gRPC
                    </label>
                  </div>

                  <div className="flex items-center gap-2">
                    <input
                      type="radio"
                      id="ws-request"
                      name="requestType"
                      value="ws-request"
                      checked={formik.values.requestType === 'ws-request'}
                      onChange={formik.handleChange}
                      data-testid="ws-request"
                    />
                    <label htmlFor="ws-request" className="ml-1 cursor-pointer select-none">
                      WebSocket
                    </label>
                  </div>
                </div>

                <div className="flex flex-col gap-2">
                  <div className="flex items-center gap-2">
                    <input
                      type="radio"
                      id="from-curl"
                      name="requestType"
                      value="from-curl"
                      checked={formik.values.requestType === 'from-curl'}
                      onChange={formik.handleChange}
                      data-testid="from-curl"
                    />
                    <label htmlFor="from-curl" className="ml-1 cursor-pointer select-none">
                      From cURL
                    </label>
                  </div>
                </div>
              </div>
            </div>
            <div className="mt-4">
              <label htmlFor="requestName" className="block font-medium">
                Request Name
              </label>
              <input
                id="request-name"
                type="text"
                name="requestName"
                placeholder="Request Name"
                ref={inputRef}
                className="block textbox mt-2 w-full"
                autoComplete="off"
                autoCorrect="off"
                autoCapitalize="off"
                spellCheck="false"
                onChange={(e) => {
                  formik.setFieldValue('requestName', e.target.value);
                  !isEditing && formik.setFieldValue('filename', sanitizeName(e.target.value));
                }}
                value={formik.values.requestName || ''}
                data-testid="request-name"
              />
              {formik.touched.requestName && formik.errors.requestName ? (
                <div className="text-red-500">{formik.errors.requestName}</div>
              ) : null}
            </div>
            {showFilesystemName && (
              <div className="mt-4">
                <div className="flex items-center justify-between">
                  <label htmlFor="filename" className="flex items-center font-medium">
                    File Name <small className="font-normal text-muted ml-1">(on filesystem)</small>
                    <Help width="300">
                      <p>Bruno saves each request as a file in your collection's folder.</p>
                      <p className="mt-2">
                        You can choose a file name different from your request's name or one compatible with filesystem
                        rules.
                      </p>
                    </Help>
                  </label>
                  {isEditing ? (
                    <IconArrowBackUp
                      className="cursor-pointer opacity-50 hover:opacity-80"
                      size={16}
                      strokeWidth={1.5}
                      onClick={() => toggleEditing(false)}
                    />
                  ) : (
                    <IconEdit
                      className="cursor-pointer opacity-50 hover:opacity-80"
                      size={16}
                      strokeWidth={1.5}
                      onClick={() => toggleEditing(true)}
                    />
                  )}
                </div>
                {isEditing ? (
                  <div className="relative flex flex-row gap-1 items-center justify-between">
                    <input
                      id="file-name"
                      type="text"
                      name="filename"
                      placeholder="File Name"
                      className="!pr-10 block textbox mt-2 w-full"
                      autoComplete="off"
                      autoCorrect="off"
                      autoCapitalize="off"
                      spellCheck="false"
                      onChange={formik.handleChange}
                      value={formik.values.filename || ''}
                      data-testid="file-name"
                    />
                    <span className="absolute right-2 top-4 flex justify-center items-center file-extension">.{collection.format}</span>
                  </div>
                ) : (
                  <div className="relative flex flex-row gap-1 items-center justify-between">
                    <PathDisplay
                      baseName={formik.values.filename ? `${formik.values.filename}.${collection.format}` : ''}
                    />
                  </div>
                )}
                {formik.touched.filename && formik.errors.filename ? (
                  <div className="text-red-500">{formik.errors.filename}</div>
                ) : null}
              </div>
            )}
            {formik.values.requestType !== 'from-curl' ? (
              <>
                <div className="mt-4">
                  <label htmlFor="request-url" className="block font-medium">
                    URL
                  </label>
                  <div className="flex items-center mt-2 ">
                    {!['grpc-request', 'ws-request'].includes(formik.values.requestType) ? (
                      <div className="flex items-center h-full method-selector-container w-1/5">
                        <HttpMethodSelector
                          method={formik.values.requestMethod}
                          onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
                        />
                      </div>
                    ) : null}
                    <div
                      id="new-request-url"
                      data-testid="new-request-url"
                      className="flex px-2 items-center flex-grow input-container h-full min-w-0"
                    >
                      <SingleLineEditor
                        onPaste={handlePaste}
                        placeholder="Request URL"
                        value={formik.values.requestUrl || ''}
                        theme={storedTheme}
                        onChange={(value) => {
                          formik.handleChange({
                            target: {
                              name: 'requestUrl',
                              value: value
                            }
                          });
                        }}
                        collection={collection}
                        variablesAutocomplete={true}
                      />
                    </div>
                  </div>
                  {formik.touched.requestUrl && formik.errors.requestUrl ? (
                    <div className="text-red-500">{formik.errors.requestUrl}</div>
                  ) : null}
                </div>
              </>
            ) : (
              <div className="mt-4">
                <div className="flex justify-between">
                  <label htmlFor="request-url" className="block font-medium">
                    cURL Command
                  </label>
                  <Dropdown className="dropdown" onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
                    <div
                      className="dropdown-item"
                      onClick={() => {
                        dropdownTippyRef.current.hide();
                        curlRequestTypeChange('http-request');
                      }}
                    >
                      HTTP
                    </div>
                    <div
                      className="dropdown-item"
                      onClick={() => {
                        dropdownTippyRef.current.hide();
                        curlRequestTypeChange('graphql-request');
                      }}
                    >
                      GraphQL
                    </div>
                  </Dropdown>
                </div>
                <textarea
                  name="curlCommand"
                  placeholder="Enter cURL request here.."
                  className="block textbox w-full mt-4 curl-command"
                  value={formik.values.curlCommand}
                  onChange={handleCurlCommandChange}
                  data-testid="curl-command"
                >
                </textarea>
                {formik.touched.curlCommand && formik.errors.curlCommand ? (
                  <div className="text-red-500">{formik.errors.curlCommand}</div>
                ) : null}
              </div>
            )}
            <div className="flex justify-between items-center mt-8 bruno-modal-footer">
              <div className="flex advanced-options">
                <Dropdown onCreate={onAdvancedDropdownCreate} icon={<AdvancedOptions />} placement="bottom-start">
                  <div
                    className="dropdown-item"
                    key="show-filesystem-name"
                    onClick={(e) => {
                      advancedDropdownTippyRef.current.hide();
                      toggleShowFilesystemName(!showFilesystemName);
                    }}
                  >
                    {showFilesystemName ? 'Hide Filesystem Name' : 'Show Filesystem Name'}
                  </div>
                </Dropdown>
              </div>
              <div className="flex justify-end">
                <span className="mr-2">
                  <button type="button" onClick={onClose} className="btn btn-md btn-close">
                    Cancel
                  </button>
                </span>
                <span>
                  <button type="submit" className="submit btn btn-md btn-secondary">
                    Create
                  </button>
                </span>
              </div>
            </div>
          </form>
        </Modal>
      </StyledWrapper>
    </Portal>
  );
};

export default NewRequest;
